/*
 * MIT License
 *
 * Copyright (c) 2024-2048 冰羽
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package cn.star.framework.support;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.json.JSONUtil;
import cn.star.framework.filter.HttpServletRequestFilter.HttpServletRequestEnhance;
import cn.star.framework.sql.Identifier;
import cn.star.framework.sql.standard.Sorter;
import cn.star.framework.sql.standard.Where;
import cn.star.framework.sql.standard.WhereGroup;
import cn.star.framework.type.BooleanOperator;
import cn.star.framework.type.DataTreeType;
import cn.star.framework.type.MvcPrefixMark;
import cn.star.framework.type.MvcPrefixMark.Keywords;
import cn.star.framework.type.OrderType;
import cn.star.framework.type.QueryType;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.Transient;
import javax.servlet.http.HttpServletRequest;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * {@link MvcPrefixMark}
 *
 * @author zhaoweiping
 *     <p style='color: red'>Created on 2024-09-23 11:06:00
 * @since 3.0.0
 */
@Slf4j
public class HttpServletRequestParse {

  private final HttpServletRequest request;
  private final Class<?> type;
  private final Map<String, Identifier> fieldsMap = new HashMap<>();
  private final Map<String, Identifier> columnMap = new HashMap<>();
  private final Map<String, String> headerMap = new HashMap<>();
  private final Map<String, String[]> parameterMap = new HashMap<>();

  public HttpServletRequestParse(HttpServletRequest request, Class<?> type) {
    this.request = request;
    this.type = type;
    this.bootstrap();
  }

  @SuppressWarnings("all")
  private void bootstrap() {
    if (type != null) {
      Field[] fields = ReflectUtil.getFields(type);
      if (ArrayUtil.isNotEmpty(fields)) {
        for (Field field : fields) {
          if (field.getAnnotation(Transient.class) == null) {
            Identifier identifier = new Identifier(type, field);
            this.fieldsMap.put(identifier.getName(), identifier);
            this.columnMap.put(identifier.getColumnName(), identifier);
          }
        }
      }
    }

    // 请求头
    Enumeration<String> enumeration = request.getHeaderNames();
    if (enumeration != null) {
      while (enumeration.hasMoreElements()) {
        String name = enumeration.nextElement();
        this.headerMap.put(name, request.getHeader(name));
      }
    }

    // 请求参数
    Map<String, String[]> map = request.getParameterMap();
    if (CollectionUtil.isNotEmpty(map)) {
      this.parameterMap.putAll(map);
    }
  }

  public String getRequestBody(HttpServletRequest request) {
    try {
      return ((HttpServletRequestEnhance) request).read("UTF-8");
    } catch (Exception e) {
      log.error("read request body error, ", e);
      return null;
    }
  }

  private BooleanOperator getWhereGroupBooleanOperator(String[] str) {
    try {
      return BooleanOperator.valueOf(str[0].toUpperCase());
    } catch (Exception e) {
      return BooleanOperator.AND;
    }
  }

  private int getWhereGroupOrder(String[] str) {
    try {
      return Integer.parseInt(str[0]);
    } catch (Exception e) {
      return -1;
    }
  }

  private QueryType getWhereQueryType(String str) {
    try {
      return QueryType.valueOf(str.toUpperCase());
    } catch (Exception e) {
      log.warn("Not support QueryType {}, use default {}", str, QueryType.EQ);
      return QueryType.EQ;
    }
  }

  private static class OrderKey {
    @Getter @Setter private String id;
    @Getter @Setter private int order = -1;

    public OrderKey(int order) {
      this(IdUtil.simpleUUID(), order);
    }

    public OrderKey(String id, int order) {
      this.id = id;
      this.order = order;
    }
  }

  private Object wrapperWhereValueForParameterMapValue(String[] values) {
    return values != null && values.length == 1 ? values[0] : values;
  }

  private WhereGroup getDefaultWhereGroup(Set<String> parameterKeys) {
    List<Where> wheres = new ArrayList<>();
    for (String key : parameterKeys) {
      String[] values = parameterMap.get(key);
      String[] keys = key.split("\\.");
      if (keys.length == 3 && MvcPrefixMark.W.getValues()[0].equalsIgnoreCase(keys[0])) {
        String other = keys[1], type = keys[2];
        Identifier identifier = this.getIdentifier(other);
        if (identifier != null) {
          wheres.add(
              new Where(
                  identifier,
                  this.getWhereQueryType(type),
                  this.wrapperWhereValueForParameterMapValue(values)));
        }
      }
    }

    return wheres.isEmpty() ? null : new WhereGroup(wheres);
  }

  private List<WhereGroup> getWhereGroups(Set<String> groups, Set<String> parameterKeys) {
    Map<OrderKey, List<WhereGroup>> whereGroupMap = new HashMap<>();

    for (String group : groups) {
      int order = -1;
      WhereGroup whereGroup = new WhereGroup(group);
      for (String key : parameterKeys) {
        String[] values = parameterMap.get(key);
        String[] keys = key.split("\\.");
        if (keys.length >= 3 && MvcPrefixMark.G.getValues()[0].equalsIgnoreCase(keys[0])) {
          String name = keys[1], other = keys[2], type = keys.length >= 4 ? keys[3] : null;
          Identifier identifier = this.getIdentifier(other);
          if (group.equals(name)) {
            if (identifier == null) {
              if (Keywords.operator.equalsIgnoreCase(other)) {
                whereGroup.setOperator(this.getWhereGroupBooleanOperator(values));
              } else if (Keywords.order.equalsIgnoreCase(other)) {
                order = this.getWhereGroupOrder(values);
              }
            } else {
              Where where =
                  new Where(
                      identifier,
                      this.getWhereQueryType(type),
                      this.wrapperWhereValueForParameterMapValue(values));

              whereGroup.getWheres().add(where);
            }
          }
        }
      }
      OrderKey key = new OrderKey(order);
      whereGroupMap.computeIfAbsent(key, k -> new ArrayList<>());
      whereGroupMap.get(key).add(whereGroup);
    }

    List<WhereGroup> list = new ArrayList<>();

    CollectionUtil.sort(
            CollectionUtil.newArrayList(whereGroupMap.keySet()),
            Comparator.comparingInt(o -> o.order))
        .forEach(key -> list.add(whereGroupMap.get(key).get(0)));

    return list;
  }

  public List<WhereGroup> getWhereGroups() {
    List<WhereGroup> whereGroups = new ArrayList<>();

    Set<String> groups = MvcPrefixMark.getGroupNames(parameterMap),
        parameterKeys = parameterMap.keySet();

    WhereGroup defaultWhereGroup = this.getDefaultWhereGroup(parameterKeys);
    if (defaultWhereGroup != null) {
      whereGroups.add(defaultWhereGroup);
    }

    List<WhereGroup> list = this.getWhereGroups(groups, parameterKeys);
    if (!list.isEmpty()) {
      whereGroups.addAll(list);
    }

    return whereGroups;
  }

  private Sorter getSorter(OrderType type, String[] names) {
    Sorter sorter = new Sorter();
    sorter.setType(type);
    if (ArrayUtil.isNotEmpty(names)) {
      for (String name : names) {
        Identifier identifier = this.getIdentifier(name);
        if (identifier != null) {
          sorter.addNames(identifier);
        }
      }
    }

    return sorter;
  }

  public List<Sorter> getSorters() {
    List<Sorter> list = new ArrayList<>();

    Sorter asc =
        this.getSorter(
            OrderType.ASC,
            this.getParameterMapValue(MvcPrefixMark.wrapper(MvcPrefixMark.S, Keywords.asc), ','));
    if (CollectionUtil.isNotEmpty(asc.getNames())) {
      list.add(asc);
    }

    Sorter desc =
        this.getSorter(
            OrderType.DESC,
            this.getParameterMapValue(MvcPrefixMark.wrapper(MvcPrefixMark.S, Keywords.desc), ','));
    if (CollectionUtil.isNotEmpty(desc.getNames())) {
      list.add(desc);
    }

    return list;
  }

  private Identifier getIdentifier(String name) {
    Identifier identifier = fieldsMap.get(name);
    if (identifier == null) {
      identifier = columnMap.get(name);
    }

    return identifier;
  }

  public List<Identifier> getQueryColumns() {
    List<Identifier> list = new ArrayList<>();

    String[] values =
        this.getParameterMapValue(MvcPrefixMark.wrapper(MvcPrefixMark.Q, Keywords.columns), ',');
    if (ArrayUtil.isNotEmpty(values)) {
      for (String value : values) {
        Identifier identifier = this.getIdentifier(value);
        if (identifier != null) {
          list.add(identifier);
        }
      }
    }

    return list;
  }

  /** 请求体 */
  public List<Map<String, Object>> getRequestBody() {
    List<Map<String, Object>> list = new ArrayList<>();
    try {
      String body = this.getRequestBody(request);
      if (StrUtil.isNotBlank(body)) {
        if (JSONUtil.isTypeJSON(body)) {
          if (JSONUtil.isTypeJSONObject(body)) {
            list.add(JSONUtil.parseObj(body));
          } else if (JSONUtil.isTypeJSONArray(body)) {
            JSONUtil.parseArray(body).forEach(item -> list.add(JSONUtil.parseObj(item.toString())));
          }
        } else {
          list.add(XmlUtil.xmlToMap(body));
        }
      }
    } catch (Exception e) {
      log.error("read request body error, ", e);
    }

    return list;
  }

  private String[] getParameterMapValue(List<String> keys) {
    return this.getParameterMapValue(keys, null);
  }

  private String[] getParameterMapValue(List<String> keys, Character regex) {
    String[] value = this.parameterMap.get(keys.remove(0));

    if (ArrayUtil.isEmpty(value) && CollectionUtil.isNotEmpty(keys)) {
      for (String str : keys) {
        value = this.parameterMap.get(str);
        if (ArrayUtil.isNotEmpty(value)) {
          break;
        }
      }
    }

    if (value != null && value.length == 1 && value[0] != null && regex != null) {
      value = value[0].split(regex.toString());
    }

    return value;
  }

  private <T> T getParameterMapValue(Class<T> type, List<String> keys) {
    return this.getParameterMapValue(type, keys, null);
  }

  @SuppressWarnings("all")
  private <T> T getParameterMapValue(Class<T> type, List<String> keys, T defaultValue) {
    String[] value = this.getParameterMapValue(keys);
    if (ArrayUtil.isNotEmpty(value)) {
      try {
        return ReflectUtil.newInstance(type, value[0]);
      } catch (Exception e) {
        log.error("{}", e.getMessage());
      }
    }

    return defaultValue;
  }

  public Integer getPageNo() {
    return this.getParameterMapValue(
        Integer.class, MvcPrefixMark.wrapper(MvcPrefixMark.P, Keywords.pageNo), 1);
  }

  public Integer getPageSize() {
    return this.getParameterMapValue(
        Integer.class, MvcPrefixMark.wrapper(MvcPrefixMark.P, Keywords.pageSize), 20);
  }

  private List<String> getParameterMapValueForX(Character regex, String... ignoreCaseNames) {
    Set<String> parameterKeys = parameterMap.keySet();
    for (String key : parameterKeys) {
      for (String name : ignoreCaseNames) {
        if (key.equalsIgnoreCase(StrUtil.format("{}.{}", MvcPrefixMark.X.getValues()[0], name))) {
          String[] values = parameterMap.get(key);
          if (regex != null && values != null && values.length == 1 && values[0] != null) {
            values = values[0].split(regex.toString());
          }
          return CollectionUtil.newArrayList(values);
        }
      }
    }

    return new ArrayList<>();
  }

  public List<Identifier> getIgnoreReadeColumns() {
    List<Identifier> list = new ArrayList<>();
    this.getParameterMapValueForX(',', Keywords.ignoreReadeColumns)
        .forEach(
            name -> {
              Identifier identifier = this.getIdentifier(name);
              if (identifier != null) {
                list.add(identifier);
              } else if (type == null) {
                list.add(new Identifier(name));
              }
            });

    return list;
  }

  public List<Identifier> getIgnoreWriteColumns() {
    List<Identifier> list = new ArrayList<>();
    this.getParameterMapValueForX(',', Keywords.ignoreWriteColumns)
        .forEach(
            name -> {
              Identifier identifier = this.getIdentifier(name);
              if (identifier != null) {
                list.add(identifier);
              } else if (type == null) {
                list.add(new Identifier(name));
              }
            });

    return list;
  }

  public DataTreeType getDataTreeType() {
    List<String> list = this.getParameterMapValueForX(',', Keywords.dataTreeType);
    if (CollectionUtil.isNotEmpty(list)) {
      return DataTreeType.find(list.get(0));
    }
    return DataTreeType.ASYNC;
  }
}
